--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit a5923e20e98a5f16eac5cb9bd7f7392f141e7f31
Parents : 8148544
Author : Ivan <ivan@quad4.io>
Signature : Invalid signer <e46112d44649266d71fe2193e00a4710>, author is <ivan@quad4.io>
Date : 2026-07-10T12:38:11-05:00
feat(docs): add new Plugins documentation and RNS Link API references, enhancing plugin capabilities and usage guidance
Changes
7 files changed, 473 insertions(+), 40 deletions(-)
Diff
diff --git a/docs/en/architecture.md b/docs/en/architecture.md
index 18aed096..7c9e02a3 100644
--- a/docs/en/architecture.md
+++ b/docs/en/architecture.md
@@ -138,7 +138,7 @@ Practical extension paths today:
- Frontend pages wired through registries
- New settings via `ConfigManager` and CLI or environment variables
- Database schema changes through migrations
-- Generic RNS Link transport over WebSocket (`rns.link.*`) for external consoles and plugins (see **RNS Link API**)
+- Generic RNS Link transport over WebSocket (`rns.link.*`) for external consoles and plugins (see [RNS Link API](rns-link-api.md))
Granted plugin manager capabilities include `destinationPath.read`, `debugLog.read`, `bugReport.*`, and `rnsLink.open` / `identify` / `request` / `send` / `close`. Hooks include `announce.received` and `rns.link.event`. Storage (`storage:isolated`) and outbound HTTP (`network:fetch`) are also grantable; install preview scans plugin files for external URLs and stores the user-selected grant subset.
@@ -152,4 +152,5 @@ The Nomad browser and Mesh Server (page nodes) share a rendering pipeline for Mi
- **Getting started** for UI navigation and first steps.
- **LXMF messaging**, **Audio calls**, and **Reticulum interfaces** for feature behaviour.
+- [Plugins](plugins.md) for extension architecture and security.
- The **Reticulum** tab in Documentation for protocol reference.
diff --git a/docs/en/plugins.md b/docs/en/plugins.md
new file mode 100644
index 00000000..e60627fd
--- /dev/null
+++ b/docs/en/plugins.md
@@ -0,0 +1,282 @@
+# Plugins
+
+Plugins extend MeshChatX with extra tools, nav items, and background behaviour. They are capability-gated: a plugin only gets what you grant at install time.
+
+Manage them from **Settings → Plugins**. Disable every packaged plugin at startup with `--disable-plugins` or `MESHCHAT_DISABLE_PLUGINS=true`.
+
+## What plugins can do
+
+- Add a row on the **Tools** page
+- Add an item in the main **Navigation** sidebar
+- React to mesh events (announces, RNS link traffic)
+- Call narrowly declared backend managers (path table, debug log, bug reports, RNS links)
+- Keep a private key-value store (`storage: isolated`)
+- Optionally fetch clearnet HTTP (`network: fetch`), still subject to **Privacy mode**
+
+Plugins cannot rewrite core MeshChatX. They do not get open-ended filesystem or process control unless you opt into Sideband Python plugins (see below).
+
+## Runtimes
+
+| Runtime | Where it runs | Trust level |
+| --------------- | ------------------------- | ----------------------------------------------- |
+| Frontend JS | Browser Web Worker | Medium. Sandboxed worker, capability grants |
+| Backend WASM | `wasmtime` on the server | Medium. Fuel-metered, capability-gated host |
+| Backend Python | In-process with MeshChatX | High. Permission-checked host, still powerful |
+| Sideband `*.py` | In-process, flat files | Highest. Opt-in danger switch, full host access |
+
+A packaged plugin can ship frontend only, backend only, or both.
+
+## Install flow
+
+```
+Pick ZIP or .wasm file in Settings → Plugins
+ |
+ --> Preview (permissions, URLs, signature, findings)
+ |
+ --> You grant or deny each capability
+ |
+ --> Optional: trust a valid signer
+ |
+ --> Install + integrity hash stored
+ |
+ --> Enable
+ |
+ --> Frontend Worker loads (if present)
+ --> Backend WASM / Python activates (if present)
+```
+
+Invalid signatures hard-block install. Unsigned packages are allowed. Present-but-broken signatures are not.
+
+After install, MeshChatX hashes the on-disk tree. If files change outside the app, the plugin is auto-disabled as tampered.
+
+## Bundled example: Bug Reports
+
+`com.meshchatx.mcx-bugs` ships with MeshChatX. It adds a **Bug Reports** tool for sending redacted debug logs to an `mcx-bugs-v1` collector, or running a collector yourself.
+
+Layout:
+
+```
+mcx-bugs/
+ plugin.json
+ frontend/main.js
+ backend/main.py
+ locales/en.json
+```
+
+Use it as the reference package when building your own.
+
+## Manifest (`plugin.json`)
+
+Every packaged plugin needs a root `plugin.json`.
+
+```json
+{
+ "id": "com.example.my-plugin",
+ "version": "1.0.0",
+ "apiVersion": 1,
+ "name": "My Plugin",
+ "description": "Adds a custom tool.",
+ "frontend": {
+ "entry": "frontend/main.js",
+ "type": "js"
+ },
+ "backend": {
+ "entry": "backend/main.py",
+ "type": "python"
+ },
+ "i18n": {
+ "directory": "locales",
+ "defaultLocale": "en"
+ },
+ "contributes": {
+ "navItems": [
+ {
+ "id": "my-plugin",
+ "route": { "name": "plugin-my-plugin" },
+ "icon": "puzzle",
+ "labelKey": "nav"
+ }
+ ],
+ "toolsPageEntries": [
+ {
+ "name": "my-plugin",
+ "route": { "name": "plugin-my-plugin" },
+ "icon": "puzzle",
+ "titleKey": "title",
+ "descriptionKey": "description"
+ }
+ ]
+ },
+ "permissions": {
+ "hooks": ["announce.received"],
+ "managers": ["destinationPath.read"],
+ "storage": "isolated",
+ "network": "none"
+ }
+}
+```
+
+Notes:
+
+- `id` is reverse-DNS style and must stay stable across versions
+- `apiVersion` is currently `1`
+- Plugin strings live in the plugin bundle (`locales/{locale}.json`), not core `en.json`
+- `contributes` wires UI slots through the frontend registries
+
+## Permissions
+
+Nothing is available unless it is declared in the manifest and granted in the install dialog.
+
+### Hooks
+
+| Hook | When it fires |
+| ------------------- | ----------------------------------------------------------- |
+| `announce.received` | A Reticulum announce arrives |
+| `rns.link.event` | Generic RNS Link traffic (`packet_received`, `link_closed`) |
+
+Hook events reach the UI as WebSocket `plugin.event` frames, then into the plugin Worker.
+
+### Managers
+
+| Manager | Purpose |
+| ---------------------- | ----------------------------- |
+| `destinationPath.read` | Read the Reticulum path table |
+| `debugLog.read` | Read redacted debug logs |
+| `bugReport.*` | Bug report / collector APIs |
+| `rnsLink.open` | Open or reuse an RNS link |
+| `rnsLink.identify` | Identify on a cached link |
+| `rnsLink.request` | Request/response on a link |
+| `rnsLink.send` | Send a raw link packet |
+| `rnsLink.close` | Tear down a cached link |
+
+Call managers from a plugin with `POST /api/v1/plugins/{id}/invoke` and `method: "callManager"`. Details for the link transport are in [RNS Link API](rns-link-api.md).
+
+### Storage and network
+
+| Permission | Effect |
+| ------------------- | --------------------------------------------------------------------- |
+| `storage: isolated` | Private key-value store in the MeshChatX database |
+| `storage: none` | No plugin storage |
+| `network: fetch` | Outbound HTTP allowed (still blocked by Privacy mode when that is on) |
+| `network: none` | No clearnet fetch |
+
+Install preview also scans plugin files for external `http://` / `https://` URLs and shows them before you grant network access.
+
+## How a frontend plugin runs
+
+```
+Settings enable plugin
+ |
+ --> PluginHost loads /api/v1/plugins
+ |
+ --> Fetch frontend entry as text
+ |
+ --> Spawn pluginWorker.js (module Worker)
+ |
+ --> Register nav / tools contributions
+ |
+ --> Subscribe to plugin.event on /ws (if hooks granted)
+ |
+ --> Worker may invoke backend via /api/v1/plugins/{id}/invoke
+```
+
+The Worker talks to the host with typed messages (`init`, `event`, `request`). The host never gives the Worker a raw privileged API.
+
+## How a backend plugin runs
+
+```
+Enable plugin
+ |
+ +--> type: wasm --> load into wasmtime, fuel + host caps
+ |
+ +--> type: python --> import entry, call activate(host)
+ |
+ --> Hooks fan out from PluginManager
+ |
+ --> invoke(method, args) for RPC from the UI Worker
+```
+
+Python host surface (permission-checked):
+
+- `host.log(message)`
+- `host.call_manager(capability, args)`
+- `host.storage_get(key)` / `host.storage_set(key, value)`
+- `host.network_fetch_allowed()`
+
+## Packaging and signing
+
+Distribute as:
+
+1. **ZIP** with `plugin.json` and assets
+2. **WASM bundle** (single `.wasm` with embedded manifest / files / optional signature)
+
+Signature file for ZIP/dir packages: `meshchatx.plugin.rsg`
+
+WASM custom sections:
+
+```
+meshchatx.plugin --> embedded plugin.json
+meshchatx.files --> embedded text assets
+meshchatx.signature --> RSG over payload without this section
+```
+
+Canonical ZIP signing uses sorted paths and fixed 1980-01-01 mtimes. The signature file itself is excluded from the signed payload.
+
+Sign and verify with:
+
+```bash
+python3 scripts/sign-plugin.py sign-dir ./my-plugin --identity <rnid>
+python3 scripts/sign-plugin.py verify-dir ./my-plugin
+python3 scripts/sign-plugin.py sign-zip ./my-plugin.zip --identity <rnid>
+python3 scripts/sign-plugin.py sign-wasm ./plugin.wasm --identity <rnid>
+python3 scripts/sign-plugin.py sign-py ./legacy_plugin.py --identity <rnid>
+```
+
+Trust status in the UI:
+
+```
+No .rsg present
+ --> Unsigned (install allowed)
+
+Valid .rsg, signer unknown
+ --> Signed (you can add to Trusted Publishers)
+
+Valid .rsg, signer in Trusted Publishers
+ --> Trusted
+
+Broken / mismatched .rsg
+ --> Invalid (install blocked)
+```
+
+## Sideband-compatible plugins
+
+Legacy Sideband-style flat `*.py` files are separate from packaged ZIP/WASM plugins.
+
+```
+Settings → Plugins → Sideband
+ |
+ --> Confirm danger prompt
+ |
+ --> Set directory of *.py files
+ |
+ --> Optional filename.py.rsg next to each script
+ |
+ --> Reload
+```
+
+These run in-process with full host access. They are not ZIP-permission gated. Keep the master switch off unless you trust every file in that directory.
+
+## Operator tips
+
+- Prefer signed packages from publishers you added yourself
+- Deny `network: fetch` unless the plugin truly needs clearnet
+- Prefer WASM backends over Python when you can
+- Use `--disable-plugins` when diagnosing weird UI or backend behaviour
+- Treat Sideband plugins like running arbitrary local scripts
+
+## See also
+
+- [Tools and utilities](tools.md) for the Tools page and contribution overview
+- [RNS Link API](rns-link-api.md) for `rnsLink.*` and `rns.link.event`
+- [Architecture and design](architecture.md) for the plugin runtime overview
+- [Identities, privacy, and security](identity-and-security.md) for signing and Privacy mode
diff --git a/docs/en/rns-link-api.md b/docs/en/rns-link-api.md
index 64376caf..4a528234 100644
--- a/docs/en/rns-link-api.md
+++ b/docs/en/rns-link-api.md
@@ -1,51 +1,152 @@
-# Generic RNS Link API
+# RNS Link API
-MeshChatX exposes a generic Reticulum Link transport over the main WebSocket (`/ws`) so external apps and plugins can open links, run request/response exchanges, send packets, and tear links down without going through NomadNet-specific helpers.
+MeshChatX exposes a generic Reticulum Link transport on the main WebSocket (`/ws`). External apps and plugins can open links, run request/response exchanges, send packets, and tear links down without going through NomadNet helpers.
-This is the surface used by microReticulum management consoles that treat MeshChatX as an RNS transport.
+## When to use it
+
+```
+Your app or plugin needs a live RNS Link
+ |
+ --> Not NomadNet page browsing
+ --> Not LXMF messaging
+ |
+ --> Use rns.link.* over /ws
+ or plugin managers rnsLink.*
+```
+
+Address peers by destination hash and aspect. Do not invent IP or hostname shortcuts.
## Auth
-When password auth is enabled, all `rns.link.*` client messages require an authenticated session (same rule as other WebSocket mutators).
+When password auth is enabled, every `rns.link.*` client message needs an authenticated session. Same rule as other WebSocket mutators.
+
+## Link lifecycle
+
+```
+Client sends rns.link.open
+ |
+ --> MeshChatX finds or opens path to destination
+ |
+ --> Link cached under (aspect, destination_hash)
+ |
+ --> Optional auto_identify
+ |
+ --> success / failure reply on same type + request_id
+ |
+ +--> rns.link.request / rns.link.send on the cached link
+ |
+ +--> rns.link.close tears down and uncaches
+ |
+ +--> disconnect cancels in-flight open / request for that client
+```
+
+Cache notes:
+
+- Key is `(aspect, destination_hash)`
+- Cap is 64 active links
+- Idle links expire after about 30 minutes
+- Repeated request failures recycle the cached link so the next call re-opens
+
+## Client to server
-## Client → server
+All messages need a unique `request_id` so replies can be matched.
-| `type` | Fields | Behavior |
-| ------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
-| `rns.link.open` | `destination_hash` (hex), `aspect` (dot-separated), `request_id`, `auto_identify?` | Open or reuse a cached link to `(aspect, destination_hash)`. Streams `phase` then `success` / `failure`. |
-| `rns.link.identify` | `destination_hash`, `aspect`, `request_id` | Call `link.identify(local_identity)` on the cached link. |
-| `rns.link.request` | `destination_hash`, `aspect`, `path`, `request_id`, `data_b64?`, `timeout?` | Ensure the link is open, then `link.request(path, data=…)`. `data_b64` / reply `body_b64` are msgpack payloads, base64-encoded. |
-| `rns.link.send` | `destination_hash`, `aspect`, `payload_b64`, `request_id` | Send a raw packet on the cached link. |
-| `rns.link.close` | `destination_hash`, `aspect`, `request_id` | Teardown and uncache the link. |
+| `type` | Required fields | Optional | Behaviour |
+| ------------------- | --------------------------------------------------------- | --------------------- | ------------------------------------------------------------------------ |
+| `rns.link.open` | `destination_hash`, `aspect`, `request_id` | `auto_identify` | Open or reuse a cached link. Streams `phase` then `success` / `failure`. |
+| `rns.link.identify` | `destination_hash`, `aspect`, `request_id` | | Call `link.identify(local_identity)` on the cached link. |
+| `rns.link.request` | `destination_hash`, `aspect`, `path`, `request_id` | `data_b64`, `timeout` | Ensure the link is open, then `link.request(path, data=…)`. |
+| `rns.link.send` | `destination_hash`, `aspect`, `payload_b64`, `request_id` | | Send a raw packet on the cached link. |
+| `rns.link.close` | `destination_hash`, `aspect`, `request_id` | | Teardown and uncache the link. |
-`aspect` is split on `.` into RNS app name + sub-aspects (for example `microrn.mgmt`).
+Field details:
-Long-running `open` / `request` work is tracked per WebSocket client and cancelled when that client disconnects.
+- `destination_hash`: hex string of the peer destination
+- `aspect`: dot-separated RNS app name + sub-aspects, for example `microrn.mgmt`
+- `data_b64` / `payload_b64` / reply `body_b64`: msgpack payloads, base64-encoded
+- `path`: request path string on the remote link endpoint
+- `timeout`: seconds for the request wait
-## Server → client
+Example open:
+
+```json
+{
+ "type": "rns.link.open",
+ "destination_hash": "aabbccddeeff00112233445566778899aabbccdd",
+ "aspect": "microrn.mgmt",
+ "request_id": "req-1",
+ "auto_identify": true
+}
+```
+
+Example request:
+
+```json
+{
+ "type": "rns.link.request",
+ "destination_hash": "aabbccddeeff00112233445566778899aabbccdd",
+ "aspect": "microrn.mgmt",
+ "path": "/status",
+ "request_id": "req-2",
+ "data_b64": null,
+ "timeout": 15
+}
+```
-Per-`request_id` replies reuse the same `type` with `status` of `phase`, `progress`, `success`, or `failure`.
+## Server to client
-Broadcast events:
+Per-`request_id` replies reuse the same `type` with a `status`:
+
+| `status` | Meaning |
+| ---------- | -------------------------------------------- |
+| `phase` | Progress step while opening or requesting |
+| `progress` | Additional progress detail when available |
+| `success` | Operation finished |
+| `failure` | Operation failed (includes an error message) |
+
+Broadcast events (not tied to one `request_id`):
| `type` | `event` | Notes |
| ---------------- | ----------------- | ---------------------- |
| `rns.link.event` | `packet_received` | Includes `payload_b64` |
| `rns.link.event` | `link_closed` | Cached link removed |
-## Plugin capabilities
+```
+Inbound packet on a cached link
+ |
+ --> Broadcast rns.link.event / packet_received
+ |
+Link torn down or evicted
+ |
+ --> Broadcast rns.link.event / link_closed
+```
-Plugins that declare the matching `permissions.managers` entries can call the same transport through `POST /api/v1/plugins/{id}/invoke` with `method: "callManager"`:
+## Plugins
-- `rnsLink.open`
-- `rnsLink.identify`
-- `rnsLink.request`
-- `rnsLink.send`
-- `rnsLink.close`
+Plugins call the same transport through HTTP invoke instead of speaking WebSocket types directly.
-Subscribe to async link traffic with `permissions.hooks: ["rns.link.event"]`. Events arrive as `plugin.event` WebSocket frames with `event: "rns.link.event"`.
+```
+Plugin Worker
+ |
+ --> POST /api/v1/plugins/{id}/invoke
+ method: "callManager"
+ |
+ --> PluginManager checks granted managers
+ |
+ --> RnsLinkManager open / identify / request / send / close
+```
-Example manifest fragment:
+Declare managers in `plugin.json`:
+
+| Manager | Maps to |
+| ------------------ | ----------------------- |
+| `rnsLink.open` | Open or reuse link |
+| `rnsLink.identify` | Identify on cached link |
+| `rnsLink.request` | Request/response |
+| `rnsLink.send` | Raw packet send |
+| `rnsLink.close` | Teardown |
+
+Subscribe to async traffic with:
```json
{
@@ -58,13 +159,57 @@ Example manifest fragment:
}
```
-## Implementation
+Hook delivery:
+
+```
+RnsLinkManager event
+ |
+ --> PluginManager.dispatch_hook("rns.link.event", …)
+ |
+ --> WebSocket plugin.event to the UI
+ |
+ --> Plugin Worker on_hook / event handler
+```
-- `meshchatx/src/backend/rns_link_manager.py` - link cache, open/identify/request/send/close
-- `meshchatx/meshchat.py` - WebSocket dispatch and per-client task tracking
-- `meshchatx/src/backend/plugin_manager.py` - capability wrappers and hook fan-out
+## External app pattern
+
+```
+Connect to MeshChatX /ws (auth cookie / session as required)
+ |
+ --> Send rns.link.open with request_id
+ |
+ --> Wait for matching success
+ |
+ --> Send rns.link.request or rns.link.send
+ |
+ --> Listen for rns.link.event broadcasts
+ |
+ --> Send rns.link.close when finished
+```
+
+Keep one `request_id` per outstanding call. Cancel or ignore replies after you disconnect. MeshChatX cancels in-flight open/request work for that WebSocket client on disconnect.
+
+## Limits and failure behaviour
+
+- Missing path or unreachable peer returns `failure` on the open/request reply
+- After repeated request failures on one cached link, MeshChatX recycles that link
+- Idle unused links are swept after about 30 minutes
+- Over-cap eviction drops the oldest unused links first
+
+## Implementation map
+
+```
+/ws rns.link.*
+ |
+ --> meshchat.py WebSocket dispatch + per-client task tracking
+ |
+ --> rns_link_manager.py cache, open, identify, request, send, close
+ |
+ --> plugin_manager.py capability wrappers + hook fan-out
+```
-## Related
+## See also
-- **Plugins** in Tools docs for install/enable flow
-- **Architecture** for the plugin runtime overview
+- [Plugins](plugins.md) for install, grants, and invoke flow
+- [Architecture and design](architecture.md) for WebSocket and plugin runtime overview
+- [Identities, privacy, and security](identity-and-security.md) for auth and session rules
diff --git a/docs/en/tools.md b/docs/en/tools.md
index e16c18a5..b35d3ec9 100644
--- a/docs/en/tools.md
+++ b/docs/en/tools.md
@@ -87,8 +87,6 @@ Optional **Sideband-compatible** plugins load flat `*.py` files from a configure
Sign packages with `scripts/sign-plugin.py` (`sign|verify` for `-dir|-zip|-wasm|-py`) using a Reticulum identity (`rnid` or in-process `RNS.Identity`). MeshChatX plugin signing uses signature file `meshchatx.plugin.rsg` and WASM custom sections `meshchatx.plugin` / `meshchatx.files` / `meshchatx.signature`.
-Plugins that need a generic Reticulum Link transport (for example a microReticulum node management UI) can request `rnsLink.*` manager capabilities and the `rns.link.event` hook. External web apps can use the same transport over `/ws` without installing a plugin. See **RNS Link API**.
-
Disable packaged plugins at startup with `--disable-plugins` if you need a minimal surface.
## Command palette
@@ -112,4 +110,5 @@ Want offline Python packages Repository server
- **Reticulum interfaces** for transport setup
- **LXMF messaging** and **Nomad Network** for feature-specific workflows
+- [Plugins](plugins.md) for extending MeshChatX functionality
- **Documentation** for offline manuals
diff --git a/docs/manifest.json b/docs/manifest.json
index feb5faf0..8a35c4f8 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -59,6 +59,16 @@
"path": "en/identity-and-security.md",
"lang": "en",
"title": { "en": "Identities, privacy, and security" }
+ },
+ {
+ "path": "en/plugins.md",
+ "lang": "en",
+ "title": { "en": "Plugins" }
+ },
+ {
+ "path": "en/rns-link-api.md",
+ "lang": "en",
+ "title": { "en": "RNS Link API" }
}
]
},
diff --git a/meshchatx/src/frontend/public/meshchatx-docs/en/rns-link-api.md b/meshchatx/src/frontend/public/meshchatx-docs/en/rns-link-api.md
index 64376caf..232aa5eb 100644
--- a/meshchatx/src/frontend/public/meshchatx-docs/en/rns-link-api.md
+++ b/meshchatx/src/frontend/public/meshchatx-docs/en/rns-link-api.md
@@ -2,8 +2,6 @@
MeshChatX exposes a generic Reticulum Link transport over the main WebSocket (`/ws`) so external apps and plugins can open links, run request/response exchanges, send packets, and tear links down without going through NomadNet-specific helpers.
-This is the surface used by microReticulum management consoles that treat MeshChatX as an RNS transport.
-
## Auth
When password auth is enabled, all `rns.link.*` client messages require an authenticated session (same rule as other WebSocket mutators).
diff --git a/meshchatx/src/frontend/public/meshchatx-docs/en/tools.md b/meshchatx/src/frontend/public/meshchatx-docs/en/tools.md
index e16c18a5..8289e5c1 100644
--- a/meshchatx/src/frontend/public/meshchatx-docs/en/tools.md
+++ b/meshchatx/src/frontend/public/meshchatx-docs/en/tools.md
@@ -87,8 +87,6 @@ Optional **Sideband-compatible** plugins load flat `*.py` files from a configure
Sign packages with `scripts/sign-plugin.py` (`sign|verify` for `-dir|-zip|-wasm|-py`) using a Reticulum identity (`rnid` or in-process `RNS.Identity`). MeshChatX plugin signing uses signature file `meshchatx.plugin.rsg` and WASM custom sections `meshchatx.plugin` / `meshchatx.files` / `meshchatx.signature`.
-Plugins that need a generic Reticulum Link transport (for example a microReticulum node management UI) can request `rnsLink.*` manager capabilities and the `rns.link.event` hook. External web apps can use the same transport over `/ws` without installing a plugin. See **RNS Link API**.
-
Disable packaged plugins at startup with `--disable-plugins` if you need a minimal surface.
## Command palette
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────